package top.cyuw.simplerpc.proxy;

import top.cyuw.simplerpc.config.RpcServiceConfig;
import top.cyuw.simplerpc.constant.RpcConfigKeyConstants;
import top.cyuw.simplerpc.exception.RpcException;
import top.cyuw.simplerpc.remoting.client.SimpleRpcClient;
import top.cyuw.simplerpc.dto.RpcRequest;
import top.cyuw.simplerpc.dto.RpcResponse;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author chen
 * @date 2023/3/14 20:49
 */
public class RpcClientProxy implements InvocationHandler {

    private final SimpleRpcClient rpcClient;
    private final RpcServiceConfig serviceConfig;

    public RpcClientProxy(SimpleRpcClient rpcClient, RpcServiceConfig serviceConfig) {
        this.rpcClient = rpcClient;
        this.serviceConfig = serviceConfig;
    }

    public <T> T getProxy(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        RpcRequest rpcRequest = RpcRequest.builder()
                .interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameters(args)
                .paramTypes(method.getParameterTypes())
                .version(serviceConfig.getVersion())
                .group(serviceConfig.getGroup())
                .build();

        CompletableFuture<RpcResponse> future = rpcClient.request(rpcRequest);

        Integer timeout = rpcClient.getContext()
                .getRpcConfig().getInteger(RpcConfigKeyConstants.REQUEST_TIMEOUT);
        if (timeout == null) {
            timeout = 0;
        }

        try {
            RpcResponse rpcResponse = future.get(timeout, TimeUnit.MILLISECONDS);
            return rpcResponse.getData();
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            throw new RpcException("invoke method failed: " + e.getMessage(), e);
        }
    }

}
